Android 代码混淆实战
一口仨馍的博客地址:
http://blog.csdn.net/qq_17250009/
Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。由于跨平台的需要,Java 字节码中包括了很多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法,这些符号带有许多语义信息,很容易被反编译成 Java 源代码。为了防止这种现象,我们可以使用 Java 混淆器对 Java 字节码进行混淆。
1.减小APK的体积
2.增加反编译后的阅读困难度(注:代码混淆并不能防止反编译)。
Eclipse 用户:
修改项目下project.properties文件,将下面代码前面的注释(#)去掉。
proguard.config=${sdk.dir}/tools/proguard/
proguard-android.txt:proguard-project.txt
Android Studio用户:
修改Module下build.gradle,将minifyEnabled设置为true。
这里以ES为例。打开proguard.project.txt。原文如下:
大概阅读一下。
注意下面这行说明:
# By default, the flags in this file are appended to
flags specified in ${sdk.dir}/tools/proguard/proguard-android.txt
这里说明我们的proguard.project.txt文件会被追加到proguard-android.txt文件下。
也就是说真正的混淆文件其实是proguard-android.txt,我们再配置的文件只是添加在proguard-android.txt文件后面。
proguard-android.txt文件只是基础配置没多少内容,有兴趣的可以去看下。
现在我们先不对混淆做任何配置,只使用默认的混淆看看有什么结果。
反编译apk查看代码步骤如下:
生成TestProguard.apk文件
修改后缀apk为zip,解压
复制解压后的classes.dex文件到dex2jar目录下[点我下载dex2jar和jd-gui](http://download.csdn.net/detail/qq_17250009/9459037)
进入dex2jar解压目录,使用dex2jar命令反编译
使用jd-gui查看源代码
dex2jar反编译命令为:
d2j-dex2jar classes.dex
使用jd-gui打开后的代码如下:
可以看到属性mName变成了a,getName()方法由于没有调用,自动被取出掉了。这就是代码混淆的好处。
你想想,如果你反编译一套代码后看见变量名甚至类名都变成了a、b、c,你还有兴趣接着看下去吗?
代码混淆生成apk之后,项目下面会多出来一个proguard文件夹,下面分别解释proguard文件夹中四个文件的作用。
dump.txt : 描述了apk中所有类 文件中内部的结构体。( Describes the internal structure of all the class files in the .apk file )
mapping.txt : 列出了原始的类、方法和名称与混淆代码间的映射。( Lists the mapping between the original and obfuscated class, method, and field names. )
seeds.txt : 列出了没有混淆的类和方法。( Lists the classes and members that are not obfuscated )
usage.txt : 列出congapk中删除的代码。( Lists the code that was stripped from the .apk )
着重注意下mapping.txt这个文件,下文会涉及到。
上面只是基础的混淆,远远不能满足实际需求。下面添加一个我们自己的混淆规则。在proguard-project.txt中添加如下代码:
-keep public class com.example.testproguard.MainActivity{
void setName();
}
乍一看不知道什么意思,对比下混淆后的代码
对比可以发现,setName()方法没有被混淆,这就是-keep关键字的作用,保持某部分代码不会混淆。
有读者可能会觉得本来就是混淆,这会怎么又不混淆了。
默认的混淆规则是:除了声明不被混淆的代码,其余的都会被混淆!比如我们在WebView中和JS交互的时候,需要提供一个接口给JS调用,如果混淆了方法名,JS就会找不到这个方法。
上面只是个示例,更多参见下文:
# 指定代码的压缩级别
-optimizationpasses 5
# 是否使用大小写混合
-dontusemixedcaseclassnames
# 是否混淆第三方jar
-dontskipnonpubliclibraryclasses
# 混淆时是否做预校验
-dontpreverify
# 混淆时是否记录日志
-verbose
# 混淆时所采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 保持哪些类不被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# 保持枚举 enum 类不被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
代码几乎都被我们混淆了,我们怎么再去查看错误日志?对着a.b.c(),鬼知道这是什么方法!骚年,别急。Google当然会为我们考虑到这种情况。下面讲解怎么查看混淆后的代码。
cmd进入sdk/tools/proguard/bin目录。
将混淆后的日志和上文提到的mapping文件放入此目录中。
执行命令:retrace.bat mapping.txt XXX.txt
执行命令前:
执行命令后:
可以看到我们发生错误的方法为initViews(),而不是a(),感谢耐心阅读到最后!